package com.github.ocean.task.config;


import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yl.common.base.enums.ResultCodeEnum;
import com.yl.common.base.model.vo.Result;
import com.yl.common.base.util.SpringBeanUtils;
import com.yl.css.report.export.config.FtExportJobConfig;
import com.yl.css.report.export.dto.ExportTaskBean;
import com.yl.css.report.export.dto.ExportTaskQueryDto;
import com.yl.css.report.export.dto.OssMultipartFile;
import com.yl.css.report.export.entity.ExportJob;
import com.yl.css.report.export.entity.FtExportParameter;
import com.yl.css.report.export.enums.FtExportJobEnum;
import com.yl.css.report.export.enums.FtExportStatusEnum;
import com.yl.css.report.export.enums.FtJobTaskConstant;
import com.yl.css.report.export.service.IFtExportJobService;
import com.yl.css.report.export.service.IFtExportParameterService;
import com.yl.css.report.export.service.OssService;
import com.yl.css.report.export.utils.ExportTaskUtils;
import com.yl.css.report.export.utils.KillHandUtils;
import com.yl.css.report.export.utils.MdcUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.stream.Collectors;


@Slf4j
@Component
public class ExportTaskExecutor {

    /**
     * 线程池是否执行:方便本地调试  1 开启
     */
    @Value("${yl.jms.ft.export.threadPoolStart:1}")
    private int threadPoolStart;

    /**
     * 线程休眠时间
     */
    //@Value("${yl.jms.ft.export.thread.SleepTime:10000}")
    private long threadSleepTime = 10000;

    /**
     * 核心线程数量
     */
    // @Value("${yl.jms.css.export.thread.corePoolSize:2}")
    private int corePoolSize = 2;
    /**
     * 最大线程数量
     */
    // @Value("${yl.jms.css.export.thread.maximumPoolSize:4}")
    private int maximumPoolSize = 4;
    /**
     * 线程池空闲时线程的存活时长
     */
    // @Value("${yl.jms.css.export.thread.keepAliveTime}")
    private long keepAliveTime = 3600;
    /**
     * 线程池队列容量
     */
    @Value("${yl.jms.css.export.thread.queue.capacity}")
    private int capacity;
    /**
     * 线程池类型: 1 SingleThreadExecutor 2 FixedThreadPool 3 自定义线程池
     */
    @Value("${yl.jms.css.export.thread.threadType:3}")
    private int threadType = 3;

    @Autowired
    private IFtExportJobService ftExportJobService;

    @Autowired
    private IFtExportParameterService ftExportParameterService;

    @Autowired
    private OssService ossService;


    public void init() {
        log.info("【导出任务管理初始化】------------------start ");
        log.info("【导出任务管理初始化】------------------threadPoolStart:{} capacity:{} threadType:{}", threadPoolStart, capacity, threadType);
        if (1 == this.threadPoolStart) {
            log.info("【导出任务管理初始化】------------------execute ");
            ExecutorService threadPool = buildThreadPool();
            ExportManageThread exportManageThread = null;
            for (int i = 0; i < this.corePoolSize; i++) {
                exportManageThread = new ExportManageThread(
                        ftExportJobService, ftExportParameterService, threadSleepTime, ossService);
                threadPool.submit(exportManageThread);
            }
        }
        log.info("【导出任务管理初始化】------------------end ");
    }


    private ExecutorService buildThreadPool() {
        BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>(capacity);
        ExecutorService pool;
        switch (this.threadType) {
            case 1: {
                pool = Executors.newSingleThreadExecutor();
                break;
            }
            case 2: {
                pool = Executors.newFixedThreadPool(corePoolSize);
                break;
            }
            default: {
                pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, blockingQueue, new ThreadPoolExecutor.AbortPolicy());
            }
        }
        return pool;
    }


    private static class ExportManageThread implements Runnable, IExportManage {

        private IPage<ExportJob> page = new Page<ExportJob>();

        /*********    以下通过初始化赋值  **********/
        private long threadSleepTime;

        private IFtExportJobService ftExportJobService;

        private IFtExportParameterService ftExportParameterService;

        private OssService ossService;

        private ExportManageThread(IFtExportJobService ftExportJobService, IFtExportParameterService ftExportParameterService,
                                   long threadSleepTime, OssService ossService) {
            this.ftExportJobService = ftExportJobService;
            this.ftExportParameterService = ftExportParameterService;
            this.threadSleepTime = threadSleepTime;
            this.ossService = ossService;
        }

        @Override
        public ExportJob taskPull(Integer status) {
            Page<ExportJob> page = new Page<>();
            page.setCurrent(1);
            page.setSize(1);
            //根据系统模块获取需要过滤的数据
            List<String> filterModelTypes = FtExportJobEnum.getEnumListBySystem().stream().map(FtExportJobEnum::getCode).collect(Collectors.toList());
            ExportTaskQueryDto ftExportJob = new ExportTaskQueryDto();
            ftExportJob.setStatusType(status);
            ftExportJob.setModelTypeList(filterModelTypes);
            page = ftExportJobService.page(page, ftExportJob);
            if (Objects.isNull(page) || CollectionUtils.isEmpty(page.getRecords())) {
                return null;
            }
            return page.getRecords().get(0);
        }

        @Override
        public ExportTaskBean taskAllow(ExportJob ftExportJob) {

            //判断是否为空
            if (Objects.isNull(ftExportJob)) {
                return null;
            }

            //获取任务执行权
            try {
                boolean result = ftExportJobService.taskStatusLock(ftExportJob);
                if (!result) {
                    log.info("【任务调度】【jobId={}】任务执行权获取失败,已被其他任务获取", ftExportJob.getId());
                    return null;
                }
            } catch (Exception e) {
                log.error("taskAllow 任务执行权获取异常:", e);
                log.info("【任务调度】【jobId={}】任务执行权获取异常", ftExportJob.getId());
                // 这里设置为执行中为了进行任务可以重试
                ftExportJob.setStatusType(FtExportStatusEnum.EXCUTE.getCode());
                return null;
            }

            ftExportJob.setStatusType(FtExportStatusEnum.EXCUTE.getCode());
            return buildExportTaskBean(ftExportJob);
        }

        @Override
        public boolean taskExecute(ExportTaskBean exportTaskBean) {
            return exportTaskBean.getTask().execute(exportTaskBean);
        }

        @Override
        public boolean taskClear(ExportTaskBean exportTaskBean) {
            return exportTaskBean.getTask().clear(exportTaskBean);
        }

        @Override
        public boolean ossPush(ExportTaskBean exportTaskBean) {
            //校验枚举是否存在
            if (Objects.isNull(exportTaskBean.getFtExportJobEnum())) {
                log.info("【导出任务调度】【jobId={}】任务枚举不存在", exportTaskBean.getFtExportJob().getId());
                return false;
            }

            //OSS推送
            try {
                byte[] bytes = ExportTaskUtils.getJobStreamByte(exportTaskBean);
                MultipartFile file = new OssMultipartFile(
                        FtJobTaskConstant.FT_EXPORT_FILE_NAME,
                        exportTaskBean.getFtExportJob().getJobName() + ExcelTypeEnum.XLSX.getValue(),
                        FtJobTaskConstant.FT_EXPORT_EXCEL_CONTENT_TYPE,
                        bytes);

                Result<String> result = ossService.upload(
                        FtJobTaskConstant.CSS_SYSTEM,
                        exportTaskBean.getFtExportJobEnum().getModel(),
                        file);

                log.info("【导出任务调度】【jobId={}】OSS推送返回结果：{}", exportTaskBean.getFtExportJob().getId(), JSON.toJSONString(result));
                if (Objects.isNull(result) || ResultCodeEnum.SUCCESS.getCode() != result.getCode()) {
                    //OSS推送返回错误
                    log.info("【导出任务调度】【jobId={}】OSS推送返回错误", exportTaskBean.getFtExportJob().getId());
                    return false;
                }

                //存储远程路径
                exportTaskBean.setFileUrl(result.getData());

            } catch (Exception e) {
                //OSS推送异常
                log.info("【导出任务调度】【jobId={}】OSS推送异常", exportTaskBean.getFtExportJob().getId(), e);
                return false;
            }

            //持久化远程路径
            exportTaskBean.getFtExportJob().setFileUrl(exportTaskBean.getFileUrl());
            exportTaskBean.getFtExportJob().setFileGenerateTime(LocalDateTime.now());
            boolean flag = ftExportJobService.updateJob(exportTaskBean.getFtExportJob());
            log.info("【导出任务调度】【jobId={}】updateJob flag:[{}]", exportTaskBean.getFtExportJob().getId(), flag);
            if (!flag) {
                log.info("【导出任务调度】【jobId={}】OSS持久化远程路径失败", exportTaskBean.getFtExportJob().getId());
            }
            return flag;
        }


        @Override
        public void run() {
            boolean result = false;
            while (true) {
                try {
                    //未进行导出任务调度
                    log.info("【导出任务调度】开始");
                    result = taskDispatch(FtExportStatusEnum.NOT_EXCUTE.getCode());
                    log.info("【导出任务调度】结果:【{}】", result);
                    //TODO 补偿
//                result = taskDispatch(FtExportStatusEnum.EXCUTE.getCode());
                } catch (Exception e) {
                    log.info("【导出任务调度】调度线程执行异常,Exception=", e);
                }
                if (!result) {
                    sleep(this.threadSleepTime);
                }
            }
        }

        /**
         * @param ftExportJob 导出任务实体
         * @create: 2020/2/21 17:13
         * @description: 构建任务bean
         * @author: TengJie.Ye
         * @retrun 导出任务Bean
         */
        private ExportTaskBean buildExportTaskBean(ExportJob ftExportJob) {
            Object obj = SpringBeanUtils.getBean(FtJobTaskConstant.FT_EXPORT_PREFIX + ftExportJob.getModelType());

            ExportTaskBean bean = new ExportTaskBean();
            bean.setFtExportJob(ftExportJob);

            //设置task
            IExportTask task = (IExportTask) obj;
            bean.setTask(task);

            FtExportJobEnum ftExportJobEnum = FtExportJobEnum.getEnumByCode(ftExportJob.getModelType());
            if (ftExportJobEnum == null) {
                return null;
            }

            //设置任务类型
            bean.setFtExportJobEnum(ftExportJobEnum);

            //获取任务参数
            FtExportParameter ftExportParameter = ftExportParameterService.getByJobId(ftExportJob.getId());
            if (Objects.isNull(ftExportParameter)) {
                log.info("【导出任务调度】【jobId={}】构建任务bean,获取任务参数为空", ftExportJob.getId());
                return null;
            }
            bean.setFtExportParameter(ftExportParameter);

            //生成文件路径
            if (!setLocalUrl(FtExportJobConfig.URL_PREFIX + bean.getFtExportJob().getModelType(), bean)) {
                log.info("【导出任务调度】【jobId={}】构建任务bean,设置文件路径失败", ftExportJob.getId());
                return null;
            }

            return bean;
        }


        private boolean setLocalUrl(String prefix, ExportTaskBean bean) {
            //不允许任务参数为空
            if (Objects.isNull(bean.getFtExportParameter())) {
                return false;
            }

            //不存在文件夹时创建
            File file = new File(prefix);
            if (!file.exists()) {
                file.mkdirs();
            }

            //保存路径
            String fileName = UUID.randomUUID().toString() + ".xlsx";
            String localUrl = prefix + "/" + fileName;
            bean.setLocalFileName(fileName);
            bean.setLocalUrl(localUrl);
            return true;
        }

        /**
         * @param status {@link FtExportStatusEnum}
         * @create: 2020/2/19 14:39
         * @description: 导出任务调度
         * @author: TengJie.Ye
         * @retrun true执行成功 false执行失败
         */
        private boolean taskDispatch(Integer status) {
            ExportTaskBean exportTaskBean = null;
            ExportJob ftExportJob = null;
            boolean result = true;
            try {
                //获取任务
                ftExportJob = taskPull(status);
                MdcUtils.setLogId(ftExportJob);
                KillHandUtils.setJob(ftExportJob);

                //是否可执行任务,否continue
                exportTaskBean = taskAllow(ftExportJob);
                if (Objects.isNull(exportTaskBean)) {
                    result = false;
                }

                //任务执行
                if (result && !taskExecute(exportTaskBean)) {
                    result = false;
                }

                //推送OSS
                if (result && !ossPush(exportTaskBean)) {
                    result = false;
                }
                //任务清理
                if (result && !taskClear(exportTaskBean)) {
                    result = false;
                }
            } catch (Exception e) {
                log.info("【导出任务调度5失败】:", e);
                //清理任务流
                ExportTaskUtils.clearJobStream(exportTaskBean);
                result = false;
            }

            if (!Objects.isNull(ftExportJob) && FtExportStatusEnum.EXCUTE.getCode().intValue() == ftExportJob.getStatusType().intValue()) {
                log.info("【导出任务调度】任务补偿 status={},sort={}", ftExportJob.getStatusType(), ftExportJob.getSort());
                if (ftExportJob.getSort() < FtExportJobConfig.getExportFailCountConfig()) {
                    ftExportJob.setStatusType(FtExportStatusEnum.NOT_EXCUTE.getCode());
                } else {
                    //超过三次失败
                    ftExportJob.setStatusType(FtExportStatusEnum.FAIL.getCode());
                }
                ftExportJob.setSort(ftExportJob.getSort() + 1);
                ftExportJob.setUpdateTime(LocalDateTime.now());
                ftExportJobService.updateJob(ftExportJob);
            }
            MdcUtils.clearLog();
            KillHandUtils.clearJob(ftExportJob);
            return result;
        }

        /**
         * @param
         * @create: 2020/2/19 14:20
         * @description: 线程休眠
         * @author: TengJie.Ye
         * @retrun
         */
        private void sleep(long millis) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                log.info("【导出任务调度】线程休眠异常,Exception=", e);
            }
        }

    }
}
